package org.itsolutions.javaBackup.properties;

import java.io.Serializable;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import org.itsolutions.javaBackup.exceptions.AException;
import org.itsolutions.javaBackup.logger.ELogLevel;
import org.itsolutions.javaBackup.logger.ILogger;
import org.itsolutions.javaBackup.logger.LoggerHelper;

/**
 * 
 * @param <T> type of property
 * @author Tomas Fecko
 */
public abstract class AProperty<T> implements Serializable {

	private static final long 						serialVersionUID = 3185664333659828634L;
	private static final ILogger 					LOGGER = LoggerHelper.getLogger(AProperty.class);
	private static final Map<String, Properties>  	PROPERTIES = new ConcurrentHashMap<String, Properties>();

	private final String 							mPropertyKey;
	private final T 								mDefaultValue;
	private T 										mValue;	
	private final List<IPropertyChangeListener<T>> 	mPropertyChangeListeners = new ArrayList<IPropertyChangeListener<T>>();

	/**
	 * 
	 * @param pPropertyKey property key
	 * @param pDefaultValue default value
	 */
	protected AProperty(String pPropertyKey, T pDefaultValue) {
		mPropertyKey = pPropertyKey;
		mDefaultValue = pDefaultValue;
		// initialize the value as soon as possible
		get();
	}

	/**
	 * 
	 * @return property group
	 */
	protected abstract String getPropertyFileName();

	/**
	 * Property key.
	 * @return key
	 */
	public final String getKey() {
		return mPropertyKey;
	}

	/**
	 * Default value of property.
	 * @return defaultValue
	 */
	public final T getDefault() {
		return mDefaultValue;
	}

	/**
	 * Value of property.
	 * @return value
	 */	
	public final T get() {
		if (mValue == null) {
			// if there is a system property with that key, return it
			String value = System.getProperty(mPropertyKey);
			if (value != null) {
				return getValue(value);
			} else {
				// read properties from file and cache it
				Properties properties = PROPERTIES.get(getPropertyFileName());
				if (properties == null) {
					properties = new Properties();
					try {
						URL url = this.getClass().getClassLoader().getResource(getPropertyFileName());
						LOGGER.log(ELogLevel.DEBUG, "Loading property file:", getPropertyFileName());
						properties.load(url.openStream());
						PROPERTIES.put(getPropertyFileName(), properties);
					} catch (Exception e) {
						LOGGER.log(ELogLevel.ERROR, "Problem during properties loading. Returning defaultValue for propertyKey:", 
								mPropertyKey, " DefaultValue:", mDefaultValue);
						return mDefaultValue;
					}
				}
				value = properties.getProperty(mPropertyKey);
				// if there is no property in config file, return default value
				if (value == null) {
					LOGGER.log(ELogLevel.WARN, "Property : ", mPropertyKey, " not found in property file:", getPropertyFileName(), 
							", using default value: ", mDefaultValue);
					return mDefaultValue;
				} else {
					return getValue(value);
				}
			}
		} else {
			return mValue;
		}
	}	

	/**
	 * Sets new value of property and notifies all the property change listeners.
	 * @param pValue new value
	 */	
	public final void set(T pValue) {
		mValue = pValue;
		for (IPropertyChangeListener<T> listener : mPropertyChangeListeners) {
			listener.propertyChanged(this);
		}
	}

	/**
	 * Registers the property change listener, which is notified when property value is changed.
	 * @param pListener listener
	 */	
	public final void registerPropertyChangedListener(IPropertyChangeListener<T> pListener) {
		mPropertyChangeListeners.add(pListener);
	}

	/**
	 * Unregisters the property change listener.
	 * @param pListener listener
	 */	
	public final void unregisterPropertyChangedListener(IPropertyChangeListener<T> pListener) {
		mPropertyChangeListeners.remove(pListener);
	}

	/**
	 * Unregisters all property change listeners.
	 */	
	public final void registerAllPropertyChangedListeners() {
		mPropertyChangeListeners.clear();
	}

	/**
	 * 
	 * @param pObj gets value from String property
	 * @return casted value
	 */
	@SuppressWarnings("unchecked")
	private T getValue(String pObj) {
		if (pObj != null) {
			try {
				if (mDefaultValue instanceof Long) {
					return (T) Long.valueOf(pObj);
				} else if (mDefaultValue instanceof Integer) {
					return (T) Integer.valueOf(pObj);
				} else if (mDefaultValue instanceof Double) {
					return (T) Double.valueOf(pObj);
				} else if (mDefaultValue instanceof Boolean) {
					return (T) Boolean.valueOf(pObj);
				} else if (mDefaultValue.getClass().isEnum() && mDefaultValue instanceof IPropertyEnum<?>) {
					return ((IPropertyEnum<T>) mDefaultValue).getValue(pObj);
				} else if (mDefaultValue instanceof List && !((List<?>) mDefaultValue).isEmpty() 
						&& ((List<?>) mDefaultValue).get(0) instanceof IPropertyEnum<?>) {
					String[] strings = pObj.split(",");
					List<IPropertyEnum<?>> returnList = new ArrayList<IPropertyEnum<?>>();
					for (String s : strings) {
						Object value = ((List<IPropertyEnum<?>>) mDefaultValue).get(0).getValue(s);
						if (value != null) {
							returnList.add((IPropertyEnum<?>) value);
						}
					}
					return (T) returnList;
				} else if (mDefaultValue instanceof String[]) {
					String[] strings = pObj.split(",");
					return (T) strings;
				} else if (mDefaultValue instanceof Integer[]) {
					String[] strings = pObj.split(",");
					Integer[] ints = new Integer[strings.length];
					for (int x = 0; x < strings.length; x++) {
						ints[x] = Integer.valueOf(strings[x]);
					}
					return (T) ints;
				} else {
					T o = (T) pObj;
					return o;
				}
			} catch (ClassCastException e) {
				LOGGER.log(ELogLevel.WARN, e, "Property with key:", mPropertyKey, " is specified wrong value:", pObj, ". Using defaultValue:", 
						mDefaultValue);
			} catch (AException e) {
				LOGGER.log(ELogLevel.ERROR, e);
			}
		}
		return mDefaultValue; 
	}

}
